﻿using System;
using System.Runtime.InteropServices;

namespace ScheduledTasksIntegrationPack
{
    #region Enums
    /// <summary>
    /// Valid types of triggers
    /// </summary>
    internal enum TriggerType
    {
        /// <summary>
        /// Trigger is set to run the task a single time. 
        /// </summary>
        RunOnce = 0,
        /// <summary>
        /// Trigger is set to run the task on a daily interval. 
        /// </summary>
        RunDaily = 1,
        /// <summary>
        /// Trigger is set to run the work item on specific days of a specific week of a specific month. 
        /// </summary>
        RunWeekly = 2,
        /// <summary>
        /// Trigger is set to run the task on a specific day(s) of the month.
        /// </summary>
        RunMonthly = 3,
        /// <summary>
        /// Trigger is set to run the task on specific days, weeks, and months.
        /// </summary>
        RunMonthlyDOW = 4,
        /// <summary>
        /// Trigger is set to run the task if the system remains idle for the amount of time specified by the idle wait time of the task.
        /// </summary>
        OnIdle = 5,
        /// <summary>
        /// Trigger is set to run the task at system startup.
        /// </summary>
        OnSystemStart = 6,
        /// <summary>
        /// Trigger is set to run the task when a user logs on. 
        /// </summary>
        OnLogon = 7
    }

    /// <summary>
    /// Values for days of the week (Monday, Tuesday, etc.)  These carry the Flags
    /// attribute so DaysOfTheWeek and be combined with | (or).
    /// </summary>
    [Flags]
    public enum DaysOfTheWeek : short
    {
        /// <summary>
        /// Sunday
        /// </summary>
        Sunday = 0x1,
        /// <summary>
        /// Monday
        /// </summary>
        Monday = 0x2,
        /// <summary>
        /// Tuesday
        /// </summary>
        Tuesday = 0x4,
        /// <summary>
        /// Wednesday
        /// </summary>
        Wednesday = 0x8,
        /// <summary>
        /// Thursday
        /// </summary>
        Thursday = 0x10,
        /// <summary>
        /// Friday
        /// </summary>
        Friday = 0x20,
        /// <summary>
        /// Saturday
        /// </summary>
        Saturday = 0x40
    }

    /// <summary>
    /// Values for week of month (first, second, ..., last)
    /// </summary>
    public enum WhichWeek : short
    {
        /// <summary>
        /// First week of the month
        /// </summary>
        FirstWeek = 1,
        /// <summary>
        /// Second week of the month
        /// </summary>
        SecondWeek = 2,
        /// <summary>
        /// Third week of the month
        /// </summary>
        ThirdWeek = 3,
        /// <summary>
        /// Fourth week of the month
        /// </summary>
        FourthWeek = 4,
        /// <summary>
        /// Last week of the month
        /// </summary>
        LastWeek = 5
    }

    /// <summary>
    /// Values for months of the year (January, February, etc.)  These carry the Flags
    /// attribute so DaysOfTheWeek and be combined with | (or).
    /// </summary>
    [Flags]
    public enum MonthsOfTheYear : short
    {
        /// <summary>
        /// January
        /// </summary>
        January = 0x1,
        /// <summary>
        /// February
        /// </summary>
        February = 0x2,
        /// <summary>
        /// March
        /// </summary>
        March = 0x4,
        /// <summary>
        /// April
        /// </summary>
        April = 0x8,
        /// <summary>
        ///May 
        /// </summary>
        May = 0x10,
        /// <summary>
        /// June
        /// </summary>
        June = 0x20,
        /// <summary>
        /// July
        /// </summary>
        July = 0x40,
        /// <summary>
        /// August
        /// </summary>
        August = 0x80,
        /// <summary>
        /// September
        /// </summary>
        September = 0x100,
        /// <summary>
        /// October
        /// </summary>
        October = 0x200,
        /// <summary>
        /// November
        /// </summary>
        November = 0x400,
        /// <summary>
        /// December
        /// </summary>
        December = 0x800
    }
    #endregion

    /// <summary>
    /// Trigger is a generalization of all the concrete trigger classes, and any actual
    /// Trigger object is one of those types.  When included in the TriggerList of a
    /// Task, a Trigger determines when a scheduled task will be run.
    /// </summary>
    /// <remarks>
    /// <para>
    /// Create a concrete trigger for a specific start condition and then call TriggerList.Add
    /// to include it in a task's TriggerList.</para>
    /// <para>
    /// A Trigger that is not yet in a Task's TriggerList is said to be unbound and it holds
    /// no resources (i.e. COM interfaces).  Once it is added to a TriggerList, it is bound and
    /// holds a COM interface that is only released when the Trigger is removed from the list or
    /// the corresponding Task is closed.</para>  
    /// <para>
    /// A Trigger that is already bound cannot be added to a TriggerList.  To copy a Trigger from
    /// one list to another, use <see cref="Clone()"/> to create an unbound copy and then add the
    /// copy to the new list.  To move a Trigger from one list to another, use <see cref="TriggerList.Remove"/>
    /// to extract the Trigger from the first list before adding it to the second.</para>
    /// </remarks>
    public abstract class Trigger : ICloneable
    {
        #region Enums
        /// <summary>
        /// Flags for triggers
        /// </summary>
        [Flags]
        private enum TaskTriggerFlags
        {
            HasEndDate = 0x1,
            KillAtDurationEnd = 0x2,
            Disabled = 0x4
        }
        #endregion

        #region Fields
        private ITaskTrigger iTaskTrigger; //null for an unbound Trigger
        internal TaskTrigger taskTrigger;
        #endregion

        #region Constructors and Initializers
        /// <summary>
        /// Internal base constructor for an unbound Trigger.
        /// </summary>
        internal Trigger()
        {
            iTaskTrigger = null;
            taskTrigger = new TaskTrigger();
            taskTrigger.TriggerSize = (ushort)Marshal.SizeOf(taskTrigger);
            taskTrigger.BeginYear = (ushort)DateTime.Today.Year;
            taskTrigger.BeginMonth = (ushort)DateTime.Today.Month;
            taskTrigger.BeginDay = (ushort)DateTime.Today.Day;
        }

        /// <summary>
        /// Internal constructor which initializes itself from
        /// from an ITaskTrigger interface.
        /// </summary>
        /// <param name="iTrigger">Instance of ITaskTrigger from system task scheduler.</param>
        internal Trigger(ITaskTrigger iTrigger)
        {
            if (iTrigger == null)
                throw new ArgumentNullException("iTrigger", "ITaskTrigger instance cannot be null");
            taskTrigger = new TaskTrigger();
            taskTrigger.TriggerSize = (ushort)Marshal.SizeOf(taskTrigger);
            iTrigger.GetTrigger(ref taskTrigger);
            iTaskTrigger = iTrigger;
        }

        #endregion

        #region Implement ICloneable
        /// <summary>
        /// Clone returns an unbound copy of the Trigger object.  It can be use
        /// on either bound or unbound original.
        /// </summary>
        /// <returns></returns>
        public object Clone()
        {
            Trigger newTrigger = (Trigger)this.MemberwiseClone();
            newTrigger.iTaskTrigger = null; // The clone is not bound
            return newTrigger;
        }
        #endregion

        #region Properties

        /// <summary>
        /// Get whether the Trigger is currently bound
        /// </summary>
        internal bool Bound
        {
            get
            {
                return iTaskTrigger != null;
            }
        }

        /// <summary>
        /// Gets/sets the beginning year, month, and day for the trigger.
        /// </summary>
        public DateTime BeginDate
        {
            get
            {
                return new DateTime(taskTrigger.BeginYear, taskTrigger.BeginMonth, taskTrigger.BeginDay);
            }
            set
            {
                taskTrigger.BeginYear = (ushort)value.Year;
                taskTrigger.BeginMonth = (ushort)value.Month;
                taskTrigger.BeginDay = (ushort)value.Day;
                SyncTrigger();
            }
        }

        /// <summary>
        /// Gets/sets indication that the task uses an EndDate.  Returns true if a value has been
        /// set for the EndDate property.  Set can only be used to turn indication off.  
        /// </summary>
        /// <exception cref="ArgumentException">Has EndDate becomes true only by setting the EndDate
        /// property.</exception>
        public bool HasEndDate
        {
            get
            {
                return ((taskTrigger.Flags & (uint)TaskTriggerFlags.HasEndDate) == (uint)TaskTriggerFlags.HasEndDate);
            }
            set
            {
                if (value)
                    throw new ArgumentException("HasEndDate can only be set false");
                taskTrigger.Flags &= ~(uint)TaskTriggerFlags.HasEndDate;
                SyncTrigger();
            }
        }

        /// <summary>
        /// Gets/sets the ending year, month, and day for the trigger.  After a value has been set
        /// with EndDate, HasEndDate becomes true.
        /// </summary>
        public DateTime EndDate
        {
            get
            {
                if (taskTrigger.EndYear == 0)
                    return DateTime.MinValue;
                return new DateTime(taskTrigger.EndYear, taskTrigger.EndMonth, taskTrigger.EndDay);
            }
            set
            {
                taskTrigger.Flags |= (uint)TaskTriggerFlags.HasEndDate;
                taskTrigger.EndYear = (ushort)value.Year;
                taskTrigger.EndMonth = (ushort)value.Month;
                taskTrigger.EndDay = (ushort)value.Day;
                SyncTrigger();
            }
        }

        /// <summary>
        /// Gets/sets the number of minutes after the trigger fires that it remains active.  Used
        /// in conjunction with <see cref="IntervalMinutes"/> to run a task repeatedly for a period of time.
        /// For example, if you want to start a task at 8:00 A.M. repeatedly restart it until 5:00 P.M.,
        /// there would be 540 minutes (9 hours) in the duration.
        /// Can also be used to terminate a task that is running when the DurationMinutes expire.  Use
        /// <see cref="KillAtDurationEnd"/> to specify that task should be terminated at that time.
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">Setting must be greater than or equal
        /// to the IntervalMinutes setting.</exception>
        public int DurationMinutes
        {
            get
            {
                return (int)taskTrigger.MinutesDuration;
            }
            set
            {
                if (value < taskTrigger.MinutesInterval)
                    throw new ArgumentOutOfRangeException("DurationMinutes", value, "DurationMinutes must be greater than or equal the IntervalMinutes value");
                taskTrigger.MinutesDuration = (uint)value;
                SyncTrigger();
            }
        }

        /// <summary>
        /// Gets/sets the number of minutes between executions for a task that is to be run repeatedly.
        /// Repetition continues until the interval specified in <see cref="DurationMinutes"/> expires.
        /// IntervalMinutes are counted from the start of the previous execution.
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">Setting must be less than
        /// to the DurationMinutes setting.</exception>
        public int IntervalMinutes
        {
            get
            {
                return (int)taskTrigger.MinutesInterval;
            }
            set
            {
                if (value > taskTrigger.MinutesDuration)
                    throw new ArgumentOutOfRangeException("IntervalMinutes", value, "IntervalMinutes must be less than or equal the DurationMinutes value");
                taskTrigger.MinutesInterval = (uint)value;
                SyncTrigger();
            }
        }

        /// <summary>
        /// Gets/sets whether task will be killed (terminated) when DurationMinutes expires. 
        /// See <see cref="Trigger.DurationMinutes"/>.
        /// </summary>
        public bool KillAtDurationEnd
        {
            get
            {
                return ((taskTrigger.Flags & (uint)TaskTriggerFlags.KillAtDurationEnd) == (uint)TaskTriggerFlags.KillAtDurationEnd);
            }
            set
            {
                if (value)
                    taskTrigger.Flags |= (uint)TaskTriggerFlags.KillAtDurationEnd;
                else
                    taskTrigger.Flags &= ~(uint)TaskTriggerFlags.KillAtDurationEnd;
                SyncTrigger();
            }
        }

        /// <summary>
        /// Gets/sets whether trigger is disabled.
        /// </summary>
        public bool Disabled
        {
            get
            {
                return ((taskTrigger.Flags & (uint)TaskTriggerFlags.Disabled) == (uint)TaskTriggerFlags.Disabled);
            }
            set
            {
                if (value)
                    taskTrigger.Flags |= (uint)TaskTriggerFlags.Disabled;
                else
                    taskTrigger.Flags &= ~(uint)TaskTriggerFlags.Disabled;
                SyncTrigger();
            }
        }
        #endregion

        #region Methods
        /// <summary>
        /// Creates a new, bound Trigger object from an ITaskTrigger interface.  The type of the
        /// concrete object created is determined by the type of ITaskTrigger.
        /// </summary>
        /// <param name="iTaskTrigger">Instance of ITaskTrigger.</param>
        /// <returns>One of the concrete classes derived from Trigger.</returns>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentException">Unable to recognize trigger type.</exception>
        internal static Trigger CreateTrigger(ITaskTrigger iTaskTrigger)
        {
            if (iTaskTrigger == null)
                throw new ArgumentNullException("iTaskTrigger", "Instance of ITaskTrigger cannot be null");
            TaskTrigger sTaskTrigger = new TaskTrigger();
            sTaskTrigger.TriggerSize = (ushort)Marshal.SizeOf(sTaskTrigger);
            iTaskTrigger.GetTrigger(ref sTaskTrigger);
            switch ((TriggerType)sTaskTrigger.Type)
            {
                case TriggerType.RunOnce:
                    return new RunOnceTrigger(iTaskTrigger);
                case TriggerType.RunDaily:
                    return new DailyTrigger(iTaskTrigger);
                case TriggerType.RunWeekly:
                    return new WeeklyTrigger(iTaskTrigger);
                case TriggerType.RunMonthlyDOW:
                    return new MonthlyDOWTrigger(iTaskTrigger);
                case TriggerType.RunMonthly:
                    return new MonthlyTrigger(iTaskTrigger);
                case TriggerType.OnIdle:
                    return new OnIdleTrigger(iTaskTrigger);
                case TriggerType.OnSystemStart:
                    return new OnSystemStartTrigger(iTaskTrigger);
                case TriggerType.OnLogon:
                    return new OnLogonTrigger(iTaskTrigger);
                default:
                    throw new ArgumentException("Unable to recognize type of trigger referenced in iTaskTrigger",
                                                "iTaskTrigger");
            }
        }

        /// <summary>
        /// When a bound Trigger is changed, the corresponding trigger in the system
        /// Task Scheduler is updated to stay in sync with the local structure.
        /// </summary>
        protected void SyncTrigger()
        {
            if (iTaskTrigger != null) iTaskTrigger.SetTrigger(ref taskTrigger);
        }

        /// <summary>
        /// Bind a Trigger object to an ITaskTrigger interface.  This causes the Trigger to
        /// sync itself with the interface and remain in sync whenever it is modified in the future.
        /// If the Trigger is already bound, an ArgumentException is thrown.
        /// </summary>
        /// <param name="iTaskTrigger">An interface representing a trigger in Task Scheduler.</param>
        /// <exception cref="ArgumentException">Attempt to bind and already bound trigger.</exception>
        internal void Bind(ITaskTrigger iTaskTrigger)
        {
            if (this.iTaskTrigger != null)
                throw new ArgumentException("Attempt to bind an already bound trigger");
            this.iTaskTrigger = iTaskTrigger;
            iTaskTrigger.SetTrigger(ref taskTrigger);
        }
        /// <summary>
        /// Bind a Trigger to the same interface the argument trigger is bound to.  
        /// </summary>
        /// <param name="trigger">A bound Trigger. </param>
        internal void Bind(Trigger trigger)
        {
            Bind(trigger.iTaskTrigger);
        }

        /// <summary>
        /// Break the connection between this Trigger and the system Task Scheduler.  This
        /// releases COM resources used in bound Triggers.
        /// </summary>
        internal void Unbind()
        {
            if (iTaskTrigger != null)
            {
                Marshal.ReleaseComObject(iTaskTrigger);
                iTaskTrigger = null;
            }
        }

        /// <summary>
        /// Gets a string, supplied by the WindowsTask Scheduler, of a bound Trigger. 
        /// For an unbound trigger, returns "Unbound Trigger".
        /// </summary>
        /// <returns>String representation of the trigger.</returns>
        public override string ToString()
        {
            if (iTaskTrigger != null)
            {
                IntPtr lpwstr;
                iTaskTrigger.GetTriggerString(out lpwstr);
                return CoTaskMem.LPWStrToString(lpwstr);
            }
            else
            {
                return "Unbound " + this.GetType().ToString();
            }
        }

        /// <summary>
        /// Determines if two triggers are internally equal.  Does not consider whether
        /// the Triggers are bound or not.
        /// </summary>
        /// <param name="obj">Value of trigger to compare.</param>
        /// <returns>true if triggers are equivalent.</returns>
        public override bool Equals(object obj)
        {
            return taskTrigger.Equals(((Trigger)obj).taskTrigger);
        }

        /// <summary>
        /// Gets a hash code for the current trigger.  A Trigger has the same hash
        /// code whether it is bound or not.
        /// </summary>
        /// <returns>Hash code value.</returns>
        public override int GetHashCode()
        {
            return taskTrigger.GetHashCode();
        }
        #endregion

    }

    /// <summary>
    /// Generalization of all triggers that have a start time.
    /// </summary>
    /// <remarks>StartableTrigger serves as a base class for triggers with a
    /// start time, but it has little use to clients.</remarks>
    public abstract class StartableTrigger : Trigger
    {
        /// <summary>
        /// Internal constructor, same as base.
        /// </summary>
        internal StartableTrigger()
            : base()
        {
        }

        /// <summary>
        /// Internal constructor from ITaskTrigger interface.
        /// </summary>
        /// <param name="iTrigger">ITaskTrigger from system Task Scheduler.</param>
        internal StartableTrigger(ITaskTrigger iTrigger)
            : base(iTrigger)
        {
        }

        /// <summary>
        /// Sets the start time of the trigger.
        /// </summary>
        /// <param name="hour">Hour of the day that the trigger will fire.</param>
        /// <param name="minute">Minute of the hour.</param>
        /// <exception cref="ArgumentOutOfRangeException">The hour is not between 0 and 23 or the minute is not between 0 and 59.</exception>
        protected void SetStartTime(ushort hour, ushort minute)
        {
            //			if (hour < 0 || hour > 23)
            //				throw new ArgumentOutOfRangeException("hour", hour, "hour must be between 0 and 23");
            //			if (minute < 0 || minute > 59)
            //				throw new ArgumentOutOfRangeException("minute", minute, "minute must be between 0 and 59");
            //			taskTrigger.StartHour = hour;
            //			taskTrigger.StartMinute = minute;
            //			base.SyncTrigger();
            StartHour = (short)hour;
            StartMinute = (short)minute;
        }

        /// <summary>
        /// Gets/sets hour of the day that trigger will fire (24 hour clock).
        /// </summary>
        public short StartHour
        {
            get
            {
                return (short)taskTrigger.StartHour;
            }
            set
            {
                if (value < 0 || value > 23)
                    throw new ArgumentOutOfRangeException("hour", value, "hour must be between 0 and 23");
                taskTrigger.StartHour = (ushort)value;
                base.SyncTrigger();
            }
        }

        /// <summary>
        /// Gets/sets minute of the hour (specified in <see cref="StartHour"/>) that trigger will fire.
        /// </summary>
        public short StartMinute
        {
            get
            {
                return (short)taskTrigger.StartMinute;
            }
            set
            {
                if (value < 0 || value > 59)
                    throw new ArgumentOutOfRangeException("minute", value, "minute must be between 0 and 59");
                taskTrigger.StartMinute = (ushort)value;
                base.SyncTrigger();
            }
        }
    }

    /// <summary>
    /// Trigger that fires once only.
    /// </summary>
    public class RunOnceTrigger : StartableTrigger
    {
        /// <summary>
        /// Create a RunOnceTrigger that fires when specified.
        /// </summary>
        /// <param name="runDateTime">Date and time to fire.</param>
        public RunOnceTrigger(DateTime runDateTime)
            : base()
        {
            taskTrigger.BeginYear = (ushort)runDateTime.Year;
            taskTrigger.BeginMonth = (ushort)runDateTime.Month;
            taskTrigger.BeginDay = (ushort)runDateTime.Day;
            SetStartTime((ushort)runDateTime.Hour, (ushort)runDateTime.Minute);
            taskTrigger.Type = TaskTriggerType.TIME_TRIGGER_ONCE;
        }

        /// <summary>
        /// Internal constructor to create from existing ITaskTrigger interface.
        /// </summary>
        /// <param name="iTrigger">ITaskTrigger from system Task Scheduler.</param>
        internal RunOnceTrigger(ITaskTrigger iTrigger)
            : base(iTrigger)
        {
        }
    }

    /// <summary>
    /// Trigger that fires at a specified time, every so many days.
    /// </summary>
    public class DailyTrigger : StartableTrigger
    {
        /// <summary>
        /// Creates a DailyTrigger that fires only at an interval of so many days.
        /// </summary>
        /// <param name="hour">Hour of day trigger will fire.</param>
        /// <param name="minutes">Minutes of the hour trigger will fire.</param>
        /// <param name="daysInterval">Number of days between task runs.</param>
        public DailyTrigger(short hour, short minutes, short daysInterval)
            : base()
        {
            SetStartTime((ushort)hour, (ushort)minutes);
            taskTrigger.Type = TaskTriggerType.TIME_TRIGGER_DAILY;
            taskTrigger.Data.daily.DaysInterval = (ushort)daysInterval;
        }

        /// <summary>
        /// Creates DailyTrigger that fires every day.
        /// </summary>
        /// <param name="hour">Hour of day trigger will fire.</param>
        /// <param name="minutes">Minutes of hour (specified in "hour") trigger will fire.</param>
        public DailyTrigger(short hour, short minutes)
            : this(hour, minutes, 1)
        {
        }

        /// <summary>
        /// Internal constructor to create from existing ITaskTrigger interface.
        /// </summary>
        /// <param name="iTrigger">ITaskTrigger from system Task Scheduler.</param>
        internal DailyTrigger(ITaskTrigger iTrigger)
            : base(iTrigger)
        {
        }

        /// <summary>
        /// Gets/sets the number of days between successive firings.
        /// </summary>
        public short DaysInterval
        {
            get
            {
                return (short)taskTrigger.Data.daily.DaysInterval;
            }
            set
            {
                taskTrigger.Data.daily.DaysInterval = (ushort)value;
                base.SyncTrigger();
            }
        }
    }

    /// <summary>
    /// Trigger that fires at a specified time, on specified days of the week,
    /// every so many weeks.
    /// </summary>
    public class WeeklyTrigger : StartableTrigger
    {
        /// <summary>
        /// Creates a WeeklyTrigger that is eligible to fire only during certain weeks.
        /// </summary>
        /// <param name="hour">Hour of day trigger will fire.</param>
        /// <param name="minutes">Minutes of hour (specified in "hour") trigger will fire.</param>
        /// <param name="daysOfTheWeek">Days of the week task will run.</param>
        /// <param name="weeksInterval">Number of weeks between task runs.</param>
        public WeeklyTrigger(short hour, short minutes, DaysOfTheWeek daysOfTheWeek, short weeksInterval)
            : base()
        {
            SetStartTime((ushort)hour, (ushort)minutes);
            taskTrigger.Type = TaskTriggerType.TIME_TRIGGER_WEEKLY;
            taskTrigger.Data.weekly.WeeksInterval = (ushort)weeksInterval;
            taskTrigger.Data.weekly.DaysOfTheWeek = (ushort)daysOfTheWeek;
        }

        /// <summary>
        /// Creates a WeeklyTrigger that is eligible to fire during any week.
        /// </summary>
        /// <param name="hour">Hour of day trigger will fire.</param>
        /// <param name="minutes">Minutes of hour (specified in "hour") trigger will fire.</param>
        /// <param name="daysOfTheWeek">Days of the week task will run.</param>
        public WeeklyTrigger(short hour, short minutes, DaysOfTheWeek daysOfTheWeek)
            : this(hour, minutes, daysOfTheWeek, 1)
        {
        }

        /// <summary>
        /// Internal constructor to create from existing ITaskTrigger interface.
        /// </summary>
        /// <param name="iTrigger">ITaskTrigger interface from system Task Scheduler.</param>
        internal WeeklyTrigger(ITaskTrigger iTrigger)
            : base(iTrigger)
        {
        }

        /// <summary>
        /// Gets/sets number of weeks from one eligible week to the next.
        /// </summary>
        public short WeeksInterval
        {
            get
            {
                return (short)taskTrigger.Data.weekly.WeeksInterval;
            }
            set
            {
                taskTrigger.Data.weekly.WeeksInterval = (ushort)value;
                base.SyncTrigger();
            }
        }

        /// <summary>
        /// Gets/sets the days of the week on which the trigger fires.
        /// </summary>
        public DaysOfTheWeek WeekDays
        {
            get
            {
                return (DaysOfTheWeek)taskTrigger.Data.weekly.DaysOfTheWeek;
            }
            set
            {
                taskTrigger.Data.weekly.DaysOfTheWeek = (ushort)value;
                base.SyncTrigger();
            }
        }
    }

    /// <summary>
    /// Trigger that fires at a specified time, on specified days of the week, 
    /// in specified weeks of the month, during specified months of the year.
    /// </summary>
    public class MonthlyDOWTrigger : StartableTrigger
    {
        /// <summary>
        /// Creates a MonthlyDOWTrigger that fires during specified months only.
        /// </summary>
        /// <param name="hour">Hour of day trigger will fire.</param>
        /// <param name="minutes">Minute of the hour trigger will fire.</param>
        /// <param name="daysOfTheWeek">Days of the week trigger will fire.</param>
        /// <param name="whichWeeks">Weeks of the month trigger will fire.</param>
        /// <param name="months">Months of the year trigger will fire.</param>
        public MonthlyDOWTrigger(short hour, short minutes, DaysOfTheWeek daysOfTheWeek, WhichWeek whichWeeks, MonthsOfTheYear months)
            : base()
        {
            SetStartTime((ushort)hour, (ushort)minutes);
            taskTrigger.Type = TaskTriggerType.TIME_TRIGGER_MONTHLYDOW;
            taskTrigger.Data.monthlyDOW.WhichWeek = (ushort)whichWeeks;
            taskTrigger.Data.monthlyDOW.DaysOfTheWeek = (ushort)daysOfTheWeek;
            taskTrigger.Data.monthlyDOW.Months = (ushort)months;
        }

        /// <summary>
        /// Creates a MonthlyDOWTrigger that fires every month.
        /// </summary>
        /// <param name="hour">Hour of day trigger will fire.</param>
        /// <param name="minutes">Minute of the hour trigger will fire.</param>
        /// <param name="daysOfTheWeek">Days of the week trigger will fire.</param>
        /// <param name="whichWeeks">Weeks of the month trigger will fire.</param>
        public MonthlyDOWTrigger(short hour, short minutes, DaysOfTheWeek daysOfTheWeek, WhichWeek whichWeeks) :
            this(hour, minutes, daysOfTheWeek, whichWeeks,
            MonthsOfTheYear.January | MonthsOfTheYear.February | MonthsOfTheYear.March | MonthsOfTheYear.April | MonthsOfTheYear.May | MonthsOfTheYear.June | MonthsOfTheYear.July | MonthsOfTheYear.August | MonthsOfTheYear.September | MonthsOfTheYear.October | MonthsOfTheYear.November | MonthsOfTheYear.December)
        {
        }

        /// <summary>
        /// Internal constructor to create from existing ITaskTrigger interface.
        /// </summary>
        /// <param name="iTrigger">ITaskTrigger from the system Task Scheduler.</param>
        internal MonthlyDOWTrigger(ITaskTrigger iTrigger)
            : base(iTrigger)
        {
        }

        /// <summary>
        /// Gets/sets weeks of the month in which trigger will fire.
        /// </summary>
        public short WhichWeeks
        {
            get
            {
                return (short)taskTrigger.Data.monthlyDOW.WhichWeek;
            }
            set
            {
                taskTrigger.Data.monthlyDOW.WhichWeek = (ushort)value;
                base.SyncTrigger();
            }
        }

        /// <summary>
        /// Gets/sets days of the week on which trigger will fire.
        /// </summary>
        public DaysOfTheWeek WeekDays
        {
            get
            {
                return (DaysOfTheWeek)taskTrigger.Data.monthlyDOW.DaysOfTheWeek;
            }
            set
            {
                taskTrigger.Data.monthlyDOW.DaysOfTheWeek = (ushort)value;
                base.SyncTrigger();
            }
        }

        /// <summary>
        /// Gets/sets months of the year in which trigger will fire.
        /// </summary>
        public MonthsOfTheYear Months
        {
            get
            {
                return (MonthsOfTheYear)taskTrigger.Data.monthlyDOW.Months;
            }
            set
            {
                taskTrigger.Data.monthlyDOW.Months = (ushort)value;
                base.SyncTrigger();
            }
        }
    }

    /// <summary>
    /// Trigger that fires at a specified time, on specified days of themonth,
    /// on specified months of the year.
    /// </summary>
    public class MonthlyTrigger : StartableTrigger
    {
        /// <summary>
        /// Creates a MonthlyTrigger that fires only during specified months of the year.
        /// </summary>
        /// <param name="hour">Hour of day trigger will fire.</param>
        /// <param name="minutes">Minutes of hour (specified in "hour") trigger will fire.</param>
        /// <param name="daysOfMonth">Days of the month trigger will fire.  (See <see cref="Days"/> property.</param>
        /// <param name="months">Months of the year trigger will fire.</param>
        public MonthlyTrigger(short hour, short minutes, int[] daysOfMonth, MonthsOfTheYear months)
            : base()
        {
            SetStartTime((ushort)hour, (ushort)minutes);
            taskTrigger.Type = TaskTriggerType.TIME_TRIGGER_MONTHLYDATE;
            taskTrigger.Data.monthlyDate.Months = (ushort)months;
            taskTrigger.Data.monthlyDate.Days = (uint)IndicesToMask(daysOfMonth);
        }

        /// <summary>
        /// Creates a MonthlyTrigger that fires during any month.
        /// </summary>
        /// <param name="hour">Hour of day trigger will fire.</param>
        /// <param name="minutes">Minutes of hour (specified in "hour") trigger will fire.</param>
        /// <param name="daysOfMonth">Days of the month trigger will fire.  (See <see cref="Days"/> property.</param>
        public MonthlyTrigger(short hour, short minutes, int[] daysOfMonth) :
            this(hour, minutes, daysOfMonth,
            MonthsOfTheYear.January | MonthsOfTheYear.February | MonthsOfTheYear.March | MonthsOfTheYear.April | MonthsOfTheYear.May | MonthsOfTheYear.June | MonthsOfTheYear.July | MonthsOfTheYear.August | MonthsOfTheYear.September | MonthsOfTheYear.October | MonthsOfTheYear.November | MonthsOfTheYear.December)
        {
        }

        /// <summary>
        /// Internal constructor to create from existing ITaskTrigger interface.
        /// </summary>
        /// <param name="iTrigger">ITaskTrigger from system Task Scheduler.</param>
        internal MonthlyTrigger(ITaskTrigger iTrigger)
            : base(iTrigger)
        {
        }


        /// <summary>
        /// Gets/sets months of the year trigger will fire.
        /// </summary>
        public MonthsOfTheYear Months
        {
            get
            {
                return (MonthsOfTheYear)taskTrigger.Data.monthlyDate.Months;
            }
            set
            {
                taskTrigger.Data.monthlyDOW.Months = (ushort)value;
                base.SyncTrigger();
            }
        }

        /// <summary>
        /// Convert an integer representing a mask to an array where each element contains the index
        /// of a bit that is ON in the mask.  Bits are considered to number from 1 to 32.
        /// </summary>
        /// <param name="mask">An interger to be interpreted as a mask.</param>
        /// <returns>An array with an element for each bit of the mask which is ON.</returns>
        private static int[] MaskToIndices(int mask)
        {
            //count bits in mask
            int cnt = 0;
            for (int i = 0; (mask >> i) > 0; i++)
                cnt = cnt + (1 & (mask >> i));
            //allocate return array with one entry for each bit
            int[] indices = new int[cnt];
            //fill array with bit indices
            cnt = 0;
            for (int i = 0; (mask >> i) > 0; i++)
                if ((1 & (mask >> i)) == 1)
                    indices[cnt++] = i + 1;
            return indices;
        }
        /// <summary>
        /// Converts an array of bit indices into a mask with bits  turned ON at every index
        /// contained in the array.  Indices must be from 1 to 32 and bits are numbered the same.
        /// </summary>
        /// <param name="indices">An array with an element for each bit of the mask which is ON.</param>
        /// <returns>An interger to be interpreted as a mask.</returns>
        private static int IndicesToMask(int[] indices)
        {
            int mask = 0;
            foreach (int index in indices)
            {
                if (index < 1 || index > 31) throw new ArgumentException("Days must be in the range 1..31");
                mask = mask | 1 << (index - 1);
            }
            return mask;
        }

        /// <summary>
        /// Gets/sets days of the month trigger will fire.
        /// </summary>
        /// <value>An array with one element for each day that the trigger will fire.
        /// The value of the element is the number of the day, in the range 1..31.</value>
        public int[] Days
        {
            get
            {
                return MaskToIndices((int)taskTrigger.Data.monthlyDate.Days);
            }
            set
            {
                taskTrigger.Data.monthlyDate.Days = (uint)IndicesToMask(value);
                base.SyncTrigger();
            }
        }
    }

    /// <summary>
    /// Trigger that fires when the system is idle for a period of time.
    /// Length of period set by <see cref="Task.IdleWaitMinutes"/>.
    /// </summary>
    public class OnIdleTrigger : Trigger
    {
        /// <summary>
        /// Creates an OnIdleTrigger.  Idle period set separately.
        /// See <see cref="Task.IdleWaitMinutes"/> inherited property.
        /// </summary>
        public OnIdleTrigger()
            : base()
        {
            taskTrigger.Type = TaskTriggerType.EVENT_TRIGGER_ON_IDLE;
        }

        /// <summary>
        /// Internal constructor to create from existing ITaskTrigger interface.
        /// </summary>
        /// <param name="iTrigger">Current base Trigger.</param>
        internal OnIdleTrigger(ITaskTrigger iTrigger)
            : base(iTrigger)
        {
        }
    }

    /// <summary>
    /// Trigger that fires when the system starts.
    /// </summary>
    public class OnSystemStartTrigger : Trigger
    {
        /// <summary>
        /// Creates an OnSystemStartTrigger.
        /// </summary>
        public OnSystemStartTrigger()
            : base()
        {
            taskTrigger.Type = TaskTriggerType.EVENT_TRIGGER_AT_SYSTEMSTART;
        }

        /// <summary>
        /// Internal constructor to create from existing ITaskTrigger interface.
        /// </summary>
        /// <param name="iTrigger">ITaskTrigger interface from system Task Scheduler.</param>
        internal OnSystemStartTrigger(ITaskTrigger iTrigger)
            : base(iTrigger)
        {
        }
    }

    /// <summary>
    /// Trigger that fires when a user logs on.
    /// </summary>
    /// <remarks>Triggers of this type fire when any user logs on, not just the
    /// user identified in the account information.</remarks>
    public class OnLogonTrigger : Trigger
    {
        /// <summary>
        /// Creates an OnLogonTrigger.
        /// </summary>
        public OnLogonTrigger()
            : base()
        {
            taskTrigger.Type = TaskTriggerType.EVENT_TRIGGER_AT_LOGON;
        }
        /// <summary>
        /// Internal constructor to create from existing ITaskTrigger interface.
        /// </summary>
        /// <param name="iTrigger">ITaskTrigger from system Task Scheduler.</param>
        internal OnLogonTrigger(ITaskTrigger iTrigger)
            : base(iTrigger)
        {
        }
    }


}